package com.briup.upload.service.impl;

import com.briup.common.exception.CustomerException;
import com.briup.common.util.ArchiveUtil;
import com.briup.common.util.Base64Util;
import com.briup.common.util.QiniuUtil;
import com.briup.common.web.MessageStatus;
import com.briup.dataset.constant.DataSetConstant;
import com.briup.dataset.entity.Classify;
import com.briup.dataset.entity.DataSet;
import com.briup.dataset.entity.Entity;
import com.briup.dataset.mapper.ClassifyMapper;
import com.briup.dataset.mapper.DataSetMapper;
import com.briup.dataset.mapper.EntityMapper;
import com.briup.dataset.mapper.extend.ClassifyExtendMapper;
import com.briup.dataset.mapper.extend.DataSetExtendMapper;
import com.briup.upload.constants.UploadConstants;
import com.briup.upload.service.IUploadService;
import org.apache.commons.io.IOUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.*;

public class UploadServiceImpl implements IUploadService {


    //本地上传的默认路径
    private String bastPath;

    // 当前设置的上传方式：local remote
    private String uploadType;

    @Autowired
    private ClassifyMapper classifyMapper;

    @Autowired
    private ClassifyExtendMapper classifyExtendMapper;

    @Autowired
    private DataSetMapper dataSetMapper;

    @Autowired
    private DataSetExtendMapper dataSetExtendMapper;

    @Autowired
    private EntityMapper entityMapper;


    public UploadServiceImpl(String bastPath,String uploadType) {
        this.bastPath = bastPath;
        this.uploadType = uploadType;
    }


    /**
     * 上传图片服务层
     * @param zip
     * @param datasetId 其他参数，例如id
     * @return 上传后每张图片的唯一标识，当前返回值在Controller中没使用到
     * @throws CustomerException
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void upload(MultipartFile[] zip,String datasetId) throws CustomerException {
        // 循环拿到每一个zip压缩包，然后开始处理，一般只会有一个zip压缩包
        if(zip!=null && zip.length>0) {
            Arrays.stream(zip).forEach(file->{
                processZip(file,datasetId);
            });
        }
    }

    /**
     * 上传单张图片
     *
     * @param image
     * @param datasetId  数据集id
     * @param classifyId 分类id
     * @return 上传文件到本地的路径
     * @throws CustomerException
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void upload(MultipartFile image, String datasetId, String classifyId) throws CustomerException {

        try {
            // 根据数据集ID查询出数据集对象
            DataSet dataSet = dataSetMapper.selectByPrimaryKey(Integer.valueOf(datasetId));
            // 根据分类ID，查询出分类对象
            Classify classify = classifyMapper.selectByPrimaryKey(Integer.valueOf(classifyId));

            // 获取图片的名字
            String originalFilename = image.getOriginalFilename();
            // 将图片转为字节数组
            byte[] bytes = image.getBytes();
            // 将字节数组转为base64字符串
            String fileBase64Str = Base64Util.byte2Base64StringFun(bytes);

            // 上传图片
            String identify = uploadImage(dataSet.getDatasetName(), classify.getClassifyName(), originalFilename, fileBase64Str);

            // 封装entity对象（图片信息）
            Entity entity = new Entity();
            entity.setEntityUuid(identify);
            entity.setClassifyId(Integer.valueOf(classifyId));

            // 插入数据库
            entityMapper.insert(entity);

        } catch (IOException e) {
            e.printStackTrace();
            throw new CustomerException(MessageStatus.UPLOAD_FILE_ERROR);
        }

    }

    /**
     * 解压压缩包，并完成后续业务逻辑
     * @param zip       压缩包
     * @param datasetId 数据集ID
     */
    private void processZip(MultipartFile zip,String datasetId) {
        // 如果不是zip压缩包，则抛出异常不处理
        if (!zip.getOriginalFilename().toUpperCase().endsWith(".ZIP")) {
            throw new CustomerException(MessageStatus.FILE_TYPE_ERROR);
        }

        // 根据数据集ID，查询出对应的数据集对象
        DataSet dataSet = dataSetMapper.selectByPrimaryKey(Integer.valueOf(datasetId));
        // 获取到数据集的名字
        String datasetName = dataSet.getDatasetName();

        // 标记当前是扩充数据集（第N次），还是新增数据集（第一次）
        // true表示本次是扩充，false表示本次是第一次
        // DatasetStatus==1表示已上传
        boolean flag = dataSet.getDatasetStatus() == 1;

        // 上传后的文件的唯一标识
        String fileIdentify = null;


        if(!zip.isEmpty()) {

            // map存放解压后的结果
            // key，是分类的名字
            // value，是list集合，里面每个值的格式为：currentClassify+" "+entryName+" "+imageBase64
            Map<String, List<String>> map = null;
            try {
                map = ArchiveUtil.unzip(zip);
            } catch (Exception e) {
                e.printStackTrace();
            }

            // 保存分类名和分类id的关系
            // 目的是为了下面封装数据的时候，需要通过分类名获取到分类的id值
            Map<String,Integer> classifyNameAndId = new HashMap<>();

            // 如果不是第一次上传，那么就是要扩展数据集中已有的分类下面的图片
            if(flag){
                // 根据数据集ID，查询出它对应的所有分类
                List<Classify> classifyList = classifyExtendMapper.selectByDataSetId(Integer.valueOf(datasetId));
                // 清洗map中的数据，因为上传的压缩包中的分类可能和已有分类不一致
                map = checkClassifyName(map,classifyList);

                // 利用查询出的classifyList来填充classifyNameAndId，目的是为了下面使用
                classifyList.forEach(classify -> {
                    classifyNameAndId.put(classify.getClassifyName(),classify.getId());
                });
            }
            // 如果是第一次，则插入classify，获得id，然后填充Map
            else {
                // 循环map，拿到所有的key值，数据库插入classify
                for(String classifyName : map.keySet()){
                    Classify classify = new Classify();
                    classify.setClassifyName(classifyName);
                    classify.setClassifyTime(new Date());
                    classify.setDatasetId(Integer.valueOf(datasetId));
                    // insert映射sql的xml标签中，添加属性：
                    // useGeneratedKeys="true" keyProperty="id"
                    classifyMapper.insert(classify);

                    // 填充classifyNameAndId集合中的值，目的是为了下面使用
                    classifyNameAndId.put(classifyName,classify.getId());
                }
            }

            // 保存即将插入数据库的Entity对象
            List<Entity> entityList = new ArrayList<>();

            // 循环map，拿到所有的value值，每个value是一个List<String>
            for(List<String> list : map.values()){
                // 循环list，拿到所有的String值，每个String是一个固定格式，空格隔开的字符串
                // 格式：currentClassify+" "+entryName+" "+imageBase64
                for(String str : list){
                    String[] arr = str.split(" ");
                    String classifyName = arr[0];
                    String fileName = arr[1];
                    String fileBase64Str = arr[2];

                    // 上传图片，返回文件上传后的唯一标识
                    fileIdentify = uploadImage(datasetName, classifyName, fileName, fileBase64Str);

                    // 创建entity对象（也就是图片）
                    Entity entity = new Entity();
                    entity.setEntityUuid(fileIdentify);
                    entity.setClassifyId(classifyNameAndId.get(classifyName));
                    // 存入集合中，准备下面的插入操作
                    entityList.add(entity);
                }
            }

            // 数据库插入entity
            for(Entity entity:entityList){
                entityMapper.insert(entity);
            }

            // 更新当前数据集的状态，改为数据集对应的分类图片已经上传
            dataSetExtendMapper.updateDatasetStatus(Integer.valueOf(datasetId), DataSetConstant.DATASET_STATUS_UPLOAD);

        }
    }

    /**
     * 上传图片到本地或者远程服务器
     * @param datasetName   数据集名字
     * @param classifyName  分类名字
     * @param fileName      图片名字
     * @param fileBase64Str 图片的base64字符串
     * @return  上传后唯一标识
     */
    private String uploadImage(String datasetName, String classifyName, String fileName, String fileBase64Str) {
        // 上传成功后的图片唯一标识
        String fileIdentify;
        // 判断是否是本地上传
        if(UploadConstants.UPLOAD_TYPE_LOCAL.equalsIgnoreCase(uploadType)){
            fileIdentify = localUpload(datasetName, classifyName, fileName, fileBase64Str);
        }
        // 判断是否是远程上传（这里使用的是七牛云图片存放服务的工具类）
        else if(UploadConstants.UPLOAD_TYPE_REMOTE.equalsIgnoreCase(uploadType)){
            fileIdentify = remoteUploadByQiNiuYun(datasetName, classifyName, fileName, fileBase64Str);
        }
        // 如果参数有误，那么就抛出异常
        else{
            throw new CustomerException(MessageStatus.UPLOAD_TYPE_ERROR);
        }
        return fileIdentify;
    }

    /**
     * 如果是扩充分类图片，本方法可以检查上传的分类名称 和 原来的分类名称是否一致，不一致的分类直接清除掉
     * @param map   当前上传的压缩包中的分类信息
     * @param classifyList  原来的分类信息
     * @return  清洗后的map，里面的分类和原有的分类保持一致
     */
    private Map<String, List<String>> checkClassifyName(Map<String, List<String>> map,List<Classify> classifyList) {
        Map<String, List<String>> newMap = new HashMap<>();
        newMap.putAll(map);

        // 如果此时上传的压缩包的分类名 和 之前数据集的分类名都不相同，那么就抛弃这个数据
        for(String key: map.keySet()){
            boolean flag = false;
            for(Classify classify : classifyList){
                if(key.equals(classify.getClassifyName())){
                    flag = true;
                    break;
                }
            }
            // 移除异常分类，因为和数据库中的不一致
            if(!flag){
                newMap.remove(key);
            }
        }

        return newMap;

    }

    /**
     * 本地上传
     * @param datasetName   数据集名字
     * @param classifyName  分类名字
     * @param fileName      图片名字
     * @param fileBase64Str 图片对应的base64字符串
     * @return 返回上传后的文件唯一表示
     */
    private String localUpload(String datasetName,String classifyName, String fileName, String fileBase64Str) {
        String filePath;
        // 防止路径后没有 /
        if(!bastPath.endsWith("/")){
            bastPath = bastPath+"/";
        }
        // 拼接文件路径
        filePath = bastPath + datasetName + "/" + classifyName + "/" + fileName;

        File newFile = new File(filePath);

        // 创建父目录
        if(!newFile.getParentFile().exists()) {
            newFile.getParentFile().mkdirs();
        }

        OutputStream out = null;
        try {
            // 将图片的base64转为对应的字节数组
            byte[] data = Base64Util.base64String2ByteFun(fileBase64Str);
            out = new FileOutputStream(newFile);
            // 完成本地上传
            IOUtils.write(data,out);
        } catch (Exception e) {
            e.printStackTrace();
            throw new CustomerException(MessageStatus.UPLOAD_FILE_ERROR);
        }finally {
            try {
                if(out!=null)out.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        // 返回本地上传后的唯一标识
        return datasetName + "/" + classifyName + "/" + fileName;
    }

    /**
     * 远程上传（七牛云）
     * @param datasetName   数据集名字
     * @param classifyName  分类名字
     * @param fileName      图片名字
     * @param fileBase64Str 图片对应的base64字符串
     * @return 返回上传后的文件唯一表示
     */
    private String remoteUploadByQiNiuYun(String datasetName, String classifyName, String fileName, String fileBase64Str) {
        // 将图片的base64转为对应的字节数组
        byte[] data = Base64Util.base64String2ByteFun(fileBase64Str);

        // 指定图片的唯一标识
        String fileIdentify = UUID.randomUUID().toString().replaceAll("-","");
        // 使用七牛云工具类完成上传
        QiniuUtil.upload2Qiniu(data,fileIdentify);

//        String fileIdentify = QiniuUtil.upload2Qiniu(data);

        // 返回七牛云上传后的唯一标识
        return fileIdentify;
    }


}
